Programas/atividades desenvolvidas para a disciplina DCA0445 - Processamento Digital de Imagens, do curso de Engenharia de Computação da Universidade Federal do Rio grande do Norte UFRN

Prefácio

Todos os programas, neste documento, foram desenvolvidas em C++ utilizando-se da biblioteca OpenCV e em ambiente Linux. Para compilar qualquer programa presente neste documento, pode-se fazer uso deste Makefile, coloca o Makefile na mesma pasta do código fonte, extensão .cpp, e execute via terminal o comando make <nome_do_programa>. Todos os códigos encontram-se no Repositório do github.

1. Programa Region

Este programa consiste em negativar uma certa região dentro de uma imagem, delimitada por um retângulo informado pelo usuário. o programa varre a área correspondente na imagem e troca os valores dos pixels para seus inversos, ou seja 255 - valor_atual.

Compilando e Executando.

$ make region
$ ./region <caminho_para_a_imagem>

O código fonte completo se encontra aqui region.cpp.

void region(Mat &img, CvPoint *p)
{
  for(unsigned int i = p[0].x; i < p[1].x; i++)
    for(unsigned int j = p[0].y; j < p[1].y; j++)
      img.at<uint8_t>(i,j) = 255 - img.at<uint8_t>(i,j);
}
toto mini
Figura 1. Entrada do programa Region
region result
Figura 2. Saída do programa Region

2. Troca Regiões

O usuário deve passar uma imagem qualquer, e o programa passara para escala de cinza e particionara a imagem em 4(quatro) partes simétricas e realizara a troca na diagonal dessas quatro subimagens.

Para este programa uma imagem foi pensada sendo composta por 4 regiões da seguinte forma:

A

B

C

D

Compilando e Executando.

$ make trocaregioes
$ ./trocaregioes <caminho_para_a_imagem>

Código completo em trocaregioes.cpp

int main(int argc, char** argv){
  Mat img;
  Mat result;
  int w, h;

  img = imread(argv[1],CV_LOAD_IMAGE_GRAYSCALE);
  //if fail to read the image
  if ( img.empty() )
  {
       cout << "Error loading the image" << endl;
       return -1;
  }

  w = img.size().width;
  h = img.size().height;
  result = img.clone();

  img(cv::Rect(0,0, w/2, h/2)).copyTo(result(cv::Rect((w-1)/2, (h-1)/2, w/2, h/2)));  (1)
  img(cv::Rect((w-1)/2, 0, w/2, h/2)).copyTo(result(cv::Rect(0, (h-1)/2, w/2, h/2))); (2)
  img(cv::Rect(0, (h-1)/2, w/2, h/2)).copyTo(result(cv::Rect((w-1)/2, 0, w/2, h/2))); (3)
  img(cv::Rect((w-1)/2, (h-1)/2, w/2, h/2)).copyTo(result(cv::Rect(0, 0, w/2, h/2))); (4)

  imshow("Original", img);
  imshow("Resultado", result);
  waitKey();

  imwrite("resultados/trocaRegiao_resultado.png", result);

  return 0;
}
1 Sobrepoe A da img original em D de result
2 Sobrepoe B da img original em C de result
3 Sobrepoe C da img original em B de result
4 Sobrepoe D da img original em A de result

Resultado

D

C

B

A

toto mini
Figura 3. Entrada do programa trocaregioes
trocaRegiao resultado
Figura 4. Saída do programa trocaregioes

3. Conta Bolhas

Este programa consiste em contar o número de regiões brancas puras, com e sem "buracos", o fundo da imagem deve ser puramente preto e os objetos puramente brancos, o programa foi testado utilizando a imagem bolhas.png. Mas o mesmo deve funcionar para qualquer imagem que siga o padrão especificado a cima.

bolhas
Figura 5. Bolhas.png

O algoritmo consiste em 4 passos bem definidos. O código completo se encontra neste link: contaregioes.cpp.

  • Passo 1- Remover objetos das bordas

//remove da borda superior e inferior
for(int i = 0; i < width; i++){
  if(image.at<uint8_t>(0, i) == OBJ_COLOR)
    floodFill(image, CvPoint(i, 0), BACK_COLOR);
  if(image.at<uint8_t>(height-1,i) == OBJ_COLOR)
    floodFill(image, CvPoint(i, height-1), BACK_COLOR);
}

//remove das laterais
for(int i = 0; i < height; i++){
  //lateral esquerda
  if(image.at<uint8_t>(i, 0) == OBJ_COLOR)
    floodFill(image, CvPoint(0, i), BACK_COLOR);
  //lateral direita
  if(image.at<uint8_t>(i, width-1) == OBJ_COLOR)
    floodFill(image, CvPoint(width-1, i), BACK_COLOR);
}
  • Passo 2- Contar bolhas com buraco

//troca o background, para facilitar a identificar os buracos das bolhas
floodFill(image, CvPoint(0,0), NEW_BACK_COLOR);
for(int i = 0; i < height; i++)
  for(int j = 0; j < width; j++)
  {
    //identifica uma bolha com buraco
    if(image.at<uint8_t>(i,j) == BACK_COLOR && image.at<uint8_t>(i,j-1) == OBJ_COLOR){
      //soma um no numero de bolhas e "apaga" a bolha encontrada
      nbolhas_com_buracos++;
      floodFill(image, CvPoint(j-1, i), NEW_BACK_COLOR);
    }
  }
  • Passo 3- Contar bolhas sem buracos

//conta bolhas sem buracos
for(int i = 0; i < height; i++)
  for(int j = 0; j < width; j++)
  {
    //identifica uma bola
    if(image.at<uint8_t>(i,j) == OBJ_COLOR){
      //soma um no numero de bolhas e "apaga" a bolha encontrada
      nbolhas_sem_buracos++;
      floodFill(image, CvPoint(j, i), NEW_BACK_COLOR);
    }
  }

Resultado

result
Figura 6. Gif das etapas (gerado com o imagemagick)
conta bolhas resultado
Figura 7. Resultado da contagem.

4. Histograma

Breve descrição do que seja um histograma…​

4.1. Equalização de Histograma

Implementação de um Equalizador de Histograma para imagens em tons de cinza.

Algoritmo de equalização, para imagens em tons de cinza:

  1. Calcular Histograma: \(h(r_k), k \in [0,255\)];

  2. Calcular Histograma Acumulado: \(ha(r_k) = \sum{h(r_j)}, j \in [0,255\)];

  3. Normalizar o Histograma Acumulado, na faixa de [0, 255]: \(ha(r_k) = ha(r_k)/ha(r_255)\);

  4. Transformar a imagem: \(f(x,y) = ha(f(x,y))\).

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int argc, char** argv){
  Mat frame;
  Mat hist;
  VideoCapture cap;
  uint8_t histEq[256];

  int histsize = 256;
  int sum;
  float range[] = {0, 256};
  const float *histrange = { range };

  cap.open(0);

  if(!cap.isOpened()){
    cout << "cameras indisponiveis\n";
    return -1;
  }

  std::cout << "Pressione qualquer tecla para encerrar o programa." << '\n';
  while(1){
    cap >> frame;

    cvtColor(frame, frame, CV_BGR2GRAY);
    imshow("Original", frame);
    //Calculo do histograma
    calcHist(&frame, 1, 0, Mat(), hist, 1, &histsize, &histrange);


    /*calculo do histograma acumulado */
    /*e normalizacao do histograma acumulado*/
    /**
     * Calcula o vetor que ira realizar a transformacao nos valores dos pixels
     */
    sum = 0;
    for(int i = 0; i < histsize; i++)
    {
      sum+= hist.at<float>(i);
      histEq[i] = sum*255.0/frame.total();
    }

    //substituicao dos valores dos pixels
    for(int i = 0; i < frame.size().height; i++)
      for(int j = 0; j < frame.size().width; j++)
        frame.at<uint8_t>(i,j) = histEq[frame.at<uint8_t>(i,j)];
    imshow("Equalizado", frame);

    if(waitKey(10) != 255)break;
  }

  return 0;
}

Antes e Depois da equalização do histograma.

equalize input
Figura 8. Entrada do programa equalize
equalize output
Figura 9. Resultado do programa equalize

Código completo: equalize.cpp

4.2. Detector de Movimento

Utilizando comparação de histogramas entre frames consecutivos, comparando-o por calculo da correlação(usando função do OpenCV, compareHist), para identificar ocorrência de movimento, para isso foi estabelecido, de forma empírica, um limiar para a correlação, ao se identificar um valor de correlação abaixo do limiar, um circulo verde é desenhado no canto superior direito da imagem, indicando uma detecção de movimento.

Makefile utilizado para compilar o programa motiondetector, é diferente pois inclui a capacidade de gerar gifs.

Motiondetector
Figura 10. Resultado do Motiondetector.

Download do código fonte: Motiondetector.cpp.

#include <iostream>
#include <opencv2/opencv.hpp>

#include <GraphicsMagick/Magick++.h>

using namespace cv;
using namespace std;

#define NUM_FRAMES_GIF 7
#define GIF_DELAY 100 //ms

int main(int argc, char** argv){
  Magick::InitializeMagick(NULL);
  vector<Magick::Image> gifFrames(NUM_FRAMES_GIF);

  Mat frame, grayFrame;
  Mat H1, H2;
  VideoCapture cap;
  int i = 0;
  double r_correl = 0;

  int histsize = 256;
  float range[] = {0, 256};
  const float *histrange = { range };

  cap.open(0);

  if(!cap.isOpened()){
    cout << "cameras indisponiveis";
    return -1;
  }

  cap >> frame; //captura um frame
  cvtColor(frame, frame, CV_BGR2GRAY); //converte para escala de cinza
  calcHist(&frame, 1, 0, Mat(), H1, 1, &histsize, &histrange);

  while(true){
    H2 = H1.clone();
    cap >> frame; //captura um frame
    cvtColor(frame, grayFrame, CV_BGR2GRAY); //converte para escala de cinza
    calcHist(&grayFrame, 1, 0, Mat(), H1, 1, &histsize, &histrange);

    r_correl =  compareHist(H1, H2, CV_COMP_CORREL);

    if(r_correl <= 0.95)//movimento
    {
      circle(frame, Point(frame.cols - 20, 20), 10, Scalar(0, 255, 0), CV_FILLED);
    }else{
      circle(frame, Point(frame.cols - 20, 20), 10, Scalar(255, 255 ,255), CV_FILLED);
    }

    imshow("Live", frame);

    //Salva o frame para o array de frames que sera usado para gerar o gif
    gifFrames[i] = Magick::Image(frame.cols,
                                 frame.rows, "BGR",
                                 Magick::StorageType::CharPixel,
                                (uint8_t*)frame.data);
    gifFrames[i].animationDelay(GIF_DELAY);
    i = (i+1)%NUM_FRAMES_GIF; //para simular uma fila circular com o vector

    if(waitKey(30) != 255)break;
  }

  //Gera o gif, colocando-o no arquivo "saidaKmeans.gif"
  Magick::writeImages(gifFrames.begin(), gifFrames.end(), "Motiondetector.gif");
  return 0;
}

5. Filtros 2D

Explicação

5.1. Laplaciano do Gaussiano (Lapgauss)

Filter2D
Figura 11. Resultado do lapgauss.cpp

código completo: lapgauss.cpp

#include <iostream>
#include <opencv2/opencv.hpp>
#include <GraphicsMagick/Magick++.h>

using namespace cv;
using namespace std;

#define MAX_FILTER 2

#define NUM_FRAMES_GIF 100
#define GIF_DELAY 10 //ms

void printmask(Mat &m){
  for(int i=0; i<m.size().height; i++){
    for(int j=0; j<m.size().width; j++){
      cout << m.at<float>(i,j) << ",";
    }
    cout << endl;
  }
}

void menu(){
  cout << "\npressione a tecla para ativar o filtro: \n"
	         "a - calcular modulo\n"
           "m - media\n"
           "g - gauss\n"
           "v - vertical\n"
	         "h - horizontal\n"
           "l - laplaciano\n"
           "i - identidade\n"
           "e - laplaciano do Gaussiano\n"
	         "esc - sair\n";
}

int main(int argvc, char** argv){
  Magick::InitializeMagick(NULL);
  vector<Magick::Image> gifFrames(NUM_FRAMES_GIF);
  int k = 0;

  VideoCapture video;
  float identidade[] = {0, 0, 0,
                        0, 1, 0,
                        0, 0, 0};
  float media[] = {1,1,1,
				           1,1,1,
				           1,1,1};
  float gauss[] = {1,2,1,
				           2,4,2,
				           1,2,1};
  float horizontal[]={-1,0,1,
					            -2,0,2,
					            -1,0,1};
  float vertical[]={-1,-2,-1,
					           0, 0, 0,
					           1, 2, 1};
  float laplacian[]={0,-1,0,
					          -1,4,-1,
					           0,-1,0};

  Mat cap, frame, frame32f, frameFiltered;
  Mat mask[] = { Mat(3,3, CV_32F), Mat(3,3, CV_32F) };
  Mat maskAux;
  Mat result;
  char* text = "Media";
  double width, height;
  int absolut;
  char key;

  video.open(0);
  if(!video.isOpened())
    return -1;
  width=video.get(CV_CAP_PROP_FRAME_WIDTH);
  height=video.get(CV_CAP_PROP_FRAME_HEIGHT);
  std::cout << "largura=" << width << "\n";;
  std::cout << "altura =" << height<< "\n";;

  namedWindow("filtroespacial",1);

  mask[0] = Mat(3, 3, CV_32F, media);
  scaleAdd(mask[0], 1/9.0, Mat::zeros(3,3,CV_32F), maskAux);
  mask[0] = maskAux.clone();
  absolut = 1; // calcs abs of the image
  mask[1] = Mat(3, 3, CV_32F, identidade);

  menu();
  for(;;){
    video >> cap;
    cvtColor(cap, frame, CV_BGR2GRAY);//converte a imagem para tons de cinza
    flip(frame, frame, 1); //espelha a imagem

    imshow("original", frame);//exibe a imagem

    frame.convertTo(frameFiltered, CV_32F); //cria uma imagem tipo float
    //aplicacao dos filtros, seguindo uma ordem, da mascara[0] ate mascara[i]
    //fazendo com que os efeitos das filtragens sejam cascateados
    for(int i = 0; i < MAX_FILTER; i++)
    {
      filter2D(frameFiltered, frameFiltered, frameFiltered.depth(), mask[i], Point(1,1), 0); //aplica o filtro 1
      if(absolut)frameFiltered = abs(frameFiltered);
    }

    frameFiltered.convertTo(result, CV_8U); //converte a imagem filtrada de float para byte em tons de cinza
    putText(result, text, Point(5,50), FONT_HERSHEY_DUPLEX, 1, Scalar(255), 2);

    imshow("filtroespacial", result);//exibe a imagem filtrada


    key = (char) waitKey(10);
    if( key == 27 ) break; // esc pressed!
    switch(key){
    case 'a':
	  menu();
      absolut=!absolut;
      break;
    case 'm':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, media);
      scaleAdd(mask[0], 1/9.0, Mat::zeros(3,3,CV_32F), maskAux);
      mask[0] = maskAux.clone();
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Media";



      break;
    case 'g':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, gauss);
      scaleAdd(mask[0], 1/16.0, Mat::zeros(3,3,CV_32F), maskAux);
      mask[0] = maskAux.clone();
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Gaussiano";
      break;
    case 'h':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, horizontal);
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Horizontal";
      break;
    case 'v':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, vertical);
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Vertical";
      break;
    case 'l':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, laplacian);
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Laplaciano";
      break;
    case 'i':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, identidade);
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Identidade";
      break;
    case 'e':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, gauss);
      scaleAdd(mask[0], 1/16.0, Mat::zeros(3,3,CV_32F), maskAux);
      mask[0] = maskAux.clone();
      mask[1] = Mat(3, 3, CV_32F, laplacian);
      text = "Laplaciano do Gaussiano";
      break;
    default:
      break;
    }


    for(int i = 0; i < height; i++)
      for(int j = 0; j < width; j++)
        result.at<uint8_t>(i,j) = 255 - result.at<uint8_t>(i,j);

    gifFrames[k] = Magick::Image(result.cols,
                                 result.rows, "K",
                                 Magick::StorageType::CharPixel,
                                (uint8_t*)result.data);

    gifFrames[k].animationDelay(GIF_DELAY);
    k = (k+1)%NUM_FRAMES_GIF; //para simular uma fila circular com o vector
  }
  //Gera o gif, colocando-o no arquivo "saidaKmeans.gif"
  Magick::writeImages(gifFrames.begin(), gifFrames.end(), "Filter2D.gif");
  return 0;
}

5.2. Tilt-Shift

Demonstração Tiltshift

Código completo: tiltshift.cpp.

#include <iostream>
#include <math.h>       /* tanh, log */
#include <opencv2/opencv.hpp>

using namespace cv;

bool tiltShift(Mat &image_src,
               Mat &image_dest,
               float delta_l, //0 to 1
               float decay,   //0 to 1
               float center,  //0 to 1
               float blurring);

void slot_sliders(int, void*);
double alfa_func(int x, int l1, int l2, double d);

Mat image, blender;
int decay_int = 50;
int center_int = 50;
int c_width_int = 50;
int blurring_int = 50;
const char mainWindow[] = "TiltSfhit";


int main(int argc, char** argv){
  const int max_slider = 100;

  if(argc != 1){//usuario passou um argumento, vou entender como sendo um endereco de uma imagem
    image = imread(argv[1]);
  }else{
    image = imread("imagens/toto_bola_cores.png");
  }

  if(image.data == NULL){
    std::cerr << "Erro ao abrir a Imagem!!" << '\n';
    return 1;
  }

  namedWindow(mainWindow, WINDOW_AUTOSIZE);
  imshow(mainWindow, image);

  createTrackbar( "Central Width [0,100]", mainWindow,
                  &c_width_int,
                  max_slider,
                  slot_sliders);

  createTrackbar( "Center [0,100]", "TiltSfhit",
        				  &center_int,
        				  max_slider,
        				  slot_sliders);

  createTrackbar( "Decay [0,100]", mainWindow,
        				  &decay_int,
        				  max_slider,
        				  slot_sliders);

  createTrackbar( "Blurring [0,100]", mainWindow,
                  &blurring_int,
                  max_slider,
                  slot_sliders);

  slot_sliders(0,0);

  waitKey(0);
  return 0;
}

void slot_sliders(int, void*){
  tiltShift(image, blender, c_width_int/100.0, decay_int/100.0, center_int/100.0, blurring_int/100.0);
  imshow(mainWindow, blender);
};

double alfa_func(int x, int l1, int l2, double d){
  return 0.5*( tanh((x-l1)/d) - tanh((x-l2)/d) );
}

bool tiltShift(Mat &image_src,
               Mat &image_dest,
               float delta_l, //0 to 1
               float decay,   //0 to 1
               float center,  //0 to 1
               float blurring)
{
  if(delta_l > 1 || center > 1 || decay > 1 || blurring > 1)return true;
  if(delta_l < 0 || center < 0 || decay < 0 || blurring < 0)return true;
  if(image_src.data == NULL)return true;

  const static int intensity_max = 100;
  Mat blurry_img;
  const int x_max = image_src.rows;
  Mat mask, image_32f;
  float weigth = 1/18.0;
  float average[] = {2, 2, 2, 2, 2, 2, 2, 2, 2};


  mask = Mat(3, 3, CV_32F, average);
  scaleAdd(mask, weigth, Mat::zeros(3,3,CV_32F), mask);
  image_src.convertTo(image_32f, CV_32F);
  for(int i = 0; i < intensity_max*blurring; i++)
  {
    filter2D(image_32f, image_32f, image_32f.depth(), mask, Point(1,1));
    image_32f = abs(image_32f);
  }
  image_32f.convertTo(blurry_img, CV_8U);

  #define L1 (center - delta_l/2.0)*x_max
  #define L2 (center + delta_l/2.0)*x_max
  image_dest = image_src.clone();
  double alfa;
  for(int x = 0; x < x_max; x++)
  {
      alfa = alfa_func(x, L1, L2, decay);
      addWeighted(  image_src.row(x),
                    alfa,
                    blurry_img.row(x),
                    1-alfa,
                    0.0,
                    image_dest.row(x));
  }

  return false;
};

5.3. Tilt-Shift em Vídeo

Demonstração Tiltshift em vídeo

Código completo em: tiltshift_video.cpp.

#include <iostream>
#include <math.h>       /* tanh, log */
#include <opencv2/opencv.hpp>

using namespace cv;

bool tiltShift(Mat &image_src,
               Mat &image_dest,
               float delta_l, //0 to 1
               float decay,   //0 to 1
               float center,  //0 to 1
               float blurring);//0 to 1

int main(int argc, char** argv){
  VideoCapture video_src;
  Mat frame;

  video_src.open(argv[1]);
  if(video_src.isOpened() == false){
    std::cerr << "Erro ao carregar arquivo de video!" << '\n';
    return 1;
  }

  int frame_width = static_cast<int>(video_src.get(CAP_PROP_FRAME_WIDTH));
  int frame_height = static_cast<int>(video_src.get(CAP_PROP_FRAME_HEIGHT));
  Size frame_size(frame_width, frame_height);
  int frames_per_second = 3;
  int steps = 10;

  //Create and initialize the VideoWriter object
  VideoWriter video_result("resultados/tiltshift_video.avi", VideoWriter::fourcc('X','V','I','D'),
                                                             frames_per_second, frame_size, true);
  if(video_result.isOpened() == false){
   std::cerr << "Erro ao carregar arquivo de video!" << '\n';
   return 1;
  }

  std::cout << "Processando..." << '\n';

  double alpha = 1.6;
  int beta = 1;

  while(true){
    video_src >> frame;
    if(frame.data == NULL)break;

    tiltShift(frame, frame, 0.3, 0.0001, 0.4, 0.15);

    for( int y = 0; y < frame.rows; y++ ) {
        for( int x = 0; x < frame.cols; x++ ) {
            for( int c = 0; c < 3; c++ ) {
                frame.at<Vec3b>(y,x)[c] =
                saturate_cast<uchar>( alpha*( frame.at<Vec3b>(y,x)[c] ) + beta );
            }
        }
    }

    video_result.write(frame);

    int i;
    for(i = 0; i < steps; i++)
    {
      if(frame.data == NULL)break; //final do arquivo
      video_src >> frame;
    }
    if(i != steps)break;
  }

  video_src.release();
  video_result.release();

  std::cout << "Terminou!" << '\n';

  system("vlc resultados/tiltshift_video.avi");

  return 0;
}


double alfa_func(int x, int l1, int l2, double d){
  return 0.5*( tanh((x-l1)/d) - tanh((x-l2)/d) );
}

bool tiltShift(Mat &image_src,
               Mat &image_dest,
               float delta_l, //0 to 1
               float decay,   //0 to 1
               float center,  //0 to 1
               float blurring)
{
  if(delta_l > 1 || center > 1 || decay > 1 || blurring > 1)return true;
  if(delta_l < 0 || center < 0 || decay < 0 || blurring < 0)return true;
  if(image_src.data == NULL)return true;

  const static int intensity_max = 100;
  Mat blurry_img;
  const int x_max = image_src.rows;
  Mat mask, image_32f;
  float weigth = 1/9.0;
  float average[] = {1, 1, 1, 1, 1, 1, 1, 1, 1};


  mask = Mat(3, 3, CV_32F, average);
  scaleAdd(mask, weigth, Mat::zeros(3,3,CV_32F), mask);
  image_src.convertTo(image_32f, CV_32F);
  for(int i = 0; i < intensity_max*blurring; i++)
  {
    filter2D(image_32f, image_32f, image_32f.depth(), mask, Point(1,1));
    image_32f = abs(image_32f);
  }
  image_32f.convertTo(blurry_img, CV_8U);

  #define L1 (center - delta_l/2.0)*x_max
  #define L2 (center + delta_l/2.0)*x_max
  image_dest = image_src.clone();
  double alfa;
  for(int x = 0; x < x_max; x++)
  {
      alfa = alfa_func(x, L1, L2, decay);
      addWeighted(  image_src.row(x),
                    alfa,
                    blurry_img.row(x),
                    1-alfa,
                    0.0,
                    image_dest.row(x));
  }

  return false;
};

6. Filtro Homomórfico

Em processo de documentação, desculpe…​

Demonstração do Filtro Homomórfico
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include <stdlib.h>     /* system, NULL, EXIT_FAILURE */

using namespace cv;
using namespace std;


Mat filter_tmp;
int percent_GamaL = 0;
int percent_GamaH = 2;
int percentD0 = 20;
int percent_C = 0;
float d_max;

void slot_sliders(int, void*);
void calcFilter(Mat& H, float gamaL, float gamaH, float D0, float c);
// troca os quadrantes da imagem da DFT
void deslocaDFT(Mat& image ){
  Mat tmp, A, B, C, D;

  // se a imagem tiver tamanho impar, recorta a regiao para
  // evitar cópias de tamanho desigual
  image = image(Rect(0, 0, image.cols & -2, image.rows & -2));
  int cx = image.cols/2;
  int cy = image.rows/2;

  // reorganiza os quadrantes da transformada
  // A B   ->  D C
  // C D       B A
  A = image(Rect(0, 0, cx, cy));
  B = image(Rect(cx, 0, cx, cy));
  C = image(Rect(0, cy, cx, cy));
  D = image(Rect(cx, cy, cx, cy));

  // A <-> D
  A.copyTo(tmp);  D.copyTo(A);  tmp.copyTo(D);

  // C <-> B
  C.copyTo(tmp);  B.copyTo(C);  tmp.copyTo(B);
}

int main(int argc, char**argv){
  const char mainWindow[] = "Filtro";
  const int max_slider = 100;
  Mat image;
  Mat padded, filter;
  Mat complexImage, complexImage_out;
  Mat_<float> realInput, zeros;
  int dft_M, dft_N;
  vector<Mat> planos;

  if(argc != 1){//usuario passou um argumento, vou entender como sendo um endereco de uma imagem
    image = imread(argv[1]);
    //converte para escala de cinza
    cvtColor(image, image, CV_BGR2GRAY);
    imshow("Entrada", image);
  }else{
    std::cerr << "Erro ao carregar imagem!" << '\n';
    return -1;
  }

  // identifica os tamanhos otimos para
  // calculo do FFT
  dft_M = getOptimalDFTSize(image.rows);
  dft_N = getOptimalDFTSize(image.cols);
  d_max = sqrt(dft_M*dft_M + dft_N*dft_N)/2.0;

  // realiza o padding da imagem
  copyMakeBorder(image, padded, 0,
                 dft_M - image.rows, 0,
                 dft_N - image.cols,
                 BORDER_CONSTANT, Scalar::all(0));

  // parte imaginaria da matriz complexa (preenchida com zeros)
  zeros = Mat_<float>::zeros(padded.size());
  // prepara a matriz complexa para ser preenchida
  complexImage = Mat(padded.size(), CV_32FC2, Scalar(0));

  // cria a componente real
  realInput = Mat_<float>(padded);
  realInput += cv::Scalar::all(1);
  cv::log(realInput, realInput);
  // combina o array de matrizes em uma unica
  // componente complexa
  Mat comps[] = {realInput, zeros};
  merge(comps, 2,  complexImage);
  // calcula o dft
  dft(complexImage, complexImage);
  // realiza a troca de quadrantes
  deslocaDFT(complexImage);
  normalize(complexImage, complexImage, 0, 1, CV_MINMAX);
  // a funcao de transferencia (filtro frequencial) deve ter o
  // mesmo tamanho e tipo da matriz complexa
  filter = complexImage.clone();

  // matriz das componentes real
  // e imaginaria do filtro
  filter_tmp = Mat(dft_M, dft_N, CV_32F);
  calcFilter(filter_tmp, percent_GamaL/100.0,
                        (percent_GamaH/100.0)*9+1,
                        (percentD0/100.0)*d_max,
                        (percent_C/100.0)*4+1);

  namedWindow(mainWindow, WINDOW_AUTOSIZE);

  createTrackbar( "Gama L [0-100%]", mainWindow,
                  &percent_GamaL,
                  max_slider,
                  slot_sliders);
  createTrackbar( "Gama H [0-100%]", mainWindow,
                  &percent_GamaH,
                  max_slider,
                  slot_sliders);
  createTrackbar( "D0 [0-100%]", mainWindow,
                  &percentD0,
                  max_slider,
                  slot_sliders);
  createTrackbar( "c [0-100%]", mainWindow,
                  &percent_C,
                  max_slider,
                  slot_sliders);

  complexImage_out = complexImage.clone();
  while(true){
    // cria a matriz com as componentes do filtro e junta
    // ambas em uma matriz multicanal complexa
    planos.clear();
    planos.push_back(filter_tmp);
    planos.push_back(filter_tmp);
    merge(planos, filter);

    // aplica o filtro frequencial
    mulSpectrums(complexImage, filter,complexImage_out,0);
    // troca novamente os quadrantes
    deslocaDFT(complexImage_out);
    // calcula a DFT inversa
    idft(complexImage_out, complexImage_out);
    // limpa o array de planos
    planos.clear();
    // separa as partes real e imaginaria da
    // imagem filtrada
    split(complexImage_out, planos);
    cv::exp(planos[0], planos[0]);
    planos[0] -= cv::Scalar::all(1);

    // normaliza a parte real para exibicao
    normalize(planos[0], planos[0], 0, 1, CV_MINMAX);

    imshow(mainWindow, filter_tmp);
    imshow("Resultado", planos[0]);
    waitKey(10);
  }

  return 0;
}

//cada slider eh mapeado de 0-100% em um range de valores
//fixos para cada uma das variaveis.
// (foi uma maneira simples que achei para controlar as variaveis com sliders de valores inteiros)
void slot_sliders(int, void*){
  calcFilter(filter_tmp, percent_GamaL/100.0,
                     (percent_GamaH/100.0)*8+2,
                     (percentD0/100.0)*d_max,
                     (percent_C/100.0)*4+1);
}

void calcFilter(Mat& H, float gamaL, float gamaH, float D0, float c){
  system("clear");
  std::cout << "Gama L:"<< gamaL << '\n';
  std::cout << "Gama H:"<< gamaH << '\n';
  std::cout << "D0:"<< D0 << '\n';
  std::cout << "C:"<< c << '\n';

  int N = H.size().width;
  int M = H.size().height;

  //Macro de calculo de distancia, com relacao ao ponto central da imagem
  #define D(u,v) sqrt( ((u)-M/2)*((u)-M/2) + ((v)-N/2)*((v)-N/2) )

  for(int u = 0; u < M; u++){
    for(int v = 0; v < N; v++){
        H.at<float>(u,v) = (gamaH - gamaL)*(1 - exp(-c*(D(u,v)*D(u,v))/(D0*D0))) + gamaL;
    }
  }
}

7. Efeito Pontilhista com detecção de bordas(Canny)

Em processo de documentação, desculpe…​

porDoSol
Figura 12. Entrada
Pontilhismo porDoSol
Figura 13. Resultado
#include <iostream>
#include <opencv2/opencv.hpp>
#include <fstream>
#include <iomanip>
#include <vector>
#include <algorithm>
#include <numeric>
#include <ctime>
#include <cstdlib>

using namespace std;
using namespace cv;

#define STEP 7
#define JITTER 3
#define RAIO_MAX 2

//funcao que desenha circulos de raio deteminados
//nas bordas destacadas pelo algoritmo de canny
//usando T1 e T2 = 3*T1, como limites infeior e superior
//respectivamente
//Os circulos sao desenhados em ordem aleatoria, para evitar efeitos padronizados
//e as cores dos circulos sao versoes levemente diferentes das tonalidades originais
//para dar efeito de erro humano
void pontilhar(const Mat &imgIn, Mat &imgOut,uint raio, uint T1);

int main(int argc, char** argv){
  vector<int> yrange;
  vector<int> xrange;

  Mat image, points, canny_img;

  uint width, height;
  uint x, y;

  image= imread(argv[1]);
  imshow("original", image);

  srand(time(0));

  if(!image.data){
    cout << "nao abriu" << argv[1] << endl;
    exit(0);
  }

  width=image.size().width;
  height=image.size().height;

  xrange.resize(height/STEP);
  yrange.resize(width/STEP);

  iota(xrange.begin(), xrange.end(), 0);
  iota(yrange.begin(), yrange.end(), 0);

  for(uint i=0; i<xrange.size(); i++){
    xrange[i]= xrange[i]*STEP+STEP/2;
  }

  for(uint i=0; i<yrange.size(); i++){
    yrange[i]= yrange[i]*STEP+STEP/2;
  }

  points =  Mat(height, width, CV_8UC3, Scalar::all(255));

  random_shuffle(xrange.begin(), xrange.end());
  for(auto i : xrange){
    random_shuffle(yrange.begin(), yrange.end());
    for(auto j : yrange){
      x = i+rand()%(2*JITTER)-JITTER +1;
      y = j+rand()%(2*JITTER)-JITTER +1;

      Vec3b val = image.at<Vec3b>(x,y);
      if(x >= height || y >= width)val = Vec3b(255,255,255);

      circle(points,
             cv::Point(y,x),
             RAIO_MAX+(rand()%2),
             val,
             -1,
             CV_AA);
    }
  }

  //3 tipos de tracos sao aplicados aqui
  //a medida que o limite inferior eh menor, mais bordas sao detectadas
  //e essas bordas serao "pintadas" com "pinceis mais grossos"
  //e para as bordas de menor limiar, "pinceis mais finos" sao usados
  pontilhar(image, points, 3, 1);
  pontilhar(image, points, 2, 20);
  pontilhar(image, points, 1, 80);

  std::string nomeArq = std::string(argv[1]);
  nomeArq = "Pontilhismo_"+nomeArq;
  imwrite(nomeArq, points);
  std::cout << "Resultado salvo em: " << nomeArq << '\n';
  return 0;
}

void pontilhar(const Mat &imgIn, Mat &imgOut, uint raio, uint T1){
  std::vector<int> xrange(imgIn.rows);
  std::vector<int> yrange(imgIn.cols);
  cv::Mat canny_img;
  uint x, y;

  iota(xrange.begin(), xrange.end(), 0);
  iota(yrange.begin(), yrange.end(), 0);

  //Destacando as bordas com o filtro de Canny
  cvtColor(imgIn, canny_img, CV_BGR2GRAY);
  Canny(canny_img, canny_img, T1, 3*T1);

  random_shuffle(xrange.begin(), xrange.end());
  for(auto i : xrange){
    random_shuffle(yrange.begin(), yrange.end());
    for(auto j : yrange){
      if(canny_img.at<uint8_t>(i,j) == 255){
        x = i+rand()%(4*raio)-1.5*raio + 1;
        y = j+rand()%(4*raio)-1.5*raio + 1;
        circle(imgOut,
          cv::Point(y,x),
          raio,
          imgIn.at<Vec3b>(i,j)*(1+(rand()%3)/10.0),  //realiza pequenas mudanças nas tonalidades originais, para dar efeito de erro humano
          -1,
          CV_AA);
      }
    }
  }
}

8. Quantização vetorial com k-means

Em processo de documentação, desculpe…​

porDoSol
Figura 14. Entrada
kmeans
Figura 15. Resultado
#include <opencv2/opencv.hpp>
#include <cstdlib>

using namespace cv;

int main( int argc, char** argv ){
  int nClusters = 10;
  Mat rotulos;
  int nRodadas = 10;
  Mat centros;
  Mat resultados[nRodadas];

  if(argc!=2)exit(0);

  Mat img = imread( argv[1], CV_LOAD_IMAGE_COLOR);

  Mat samples(img.rows * img.cols, 3, CV_32F);

  //preenche o vetor de amostras
  for( int y = 0; y < img.rows; y++ ){
    for( int x = 0; x < img.cols; x++ ){
      for( int z = 0; z < 3; z++){
        samples.at<float>(y + x*img.rows, z) = img.at<Vec3b>(y,x)[z];
	  }
	 }
  }

  //Realiza nRodadas de calculo de kmeans, cada uma, muito provavelmente, retornara
  //um valor diferente para cada media represntativa, pra cada uma das nClusters classes
  for(uint i = 0; i < nRodadas; i++)
  {
    kmeans(samples,
      nClusters,
      rotulos,
      TermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS, 10000, 0.0001),
      1,
      KMEANS_RANDOM_CENTERS,
      centros );

      Mat rotulada( img.size(), img.type() );
      for( int y = 0; y < img.rows; y++ ){
        for( int x = 0; x < img.cols; x++ ){
          int indice = rotulos.at<int>(y + x*img.rows,0);
          rotulada.at<Vec3b>(y,x)[0] = (uchar) centros.at<float>(indice, 0);
          rotulada.at<Vec3b>(y,x)[1] = (uchar) centros.at<float>(indice, 1);
          rotulada.at<Vec3b>(y,x)[2] = (uchar) centros.at<float>(indice, 2);
        }
      }
      //salva no vetor resultados, para posteriormente virarem um gif
      rotulada.copyTo(resultados[i]);
  }
  //salva cada iteraca em um arquivo png
  //para posteriormente virarem um gif
  //com a ferramenta imageMagick por meio do
  //do programa via terminal, convert.
  for(uint i = 0; i < nRodadas; i++)
    imwrite("rodada-"+std::to_string(i)+".png", resultados[i]);
}

9. Bibliografia

  • Rafael Gonzalez. 'Processamento Digital de Imagens'. Addison-Wesley. 1990. 2 ed.